﻿#region

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Net;
using System.Net.Mail;

#endregion

namespace MailQ.API
{
	/// <summary>
	/// </summary>
	public class EmailHelper
	{
		#region AddressTypes enum

		/// <summary>
		/// </summary>
		public enum AddressTypes
		{
			To,
			Bcc,
			Cc
		}

		#endregion

		#region QueueStates enum

		/// <summary>
		/// </summary>
		public enum QueueStates
		{
			Unsent = 1,
			Sending = 2,
			Sent = 3,
			Failed = 4
		}

		#endregion

		/// <summary>
		/// </summary>
		/// <param name = "FailedMessages"></param>
		public static void UpdateFailedMessages(Dictionary<int, MailMessage> FailedMessages)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			var Messages = from Msg in Mqdb.Messages
						   where (FailedMessages.Keys.Contains(Msg.MessageID))
						   select Msg;

			foreach (Message Msg in Messages)
			{
				Msg.QueueStateID = (int)QueueStates.Failed;
				Msg.SendAttempts += 1;
			}

			Mqdb.SubmitChanges();
		}

		/// <summary>
		/// </summary>
		/// <param name = "SentMessages"></param>
		public static void UpdateSentMessages(Dictionary<int, MailMessage> SentMessages)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			var Messages = from Msg in Mqdb.Messages
						   where SentMessages.Keys.Contains(Msg.MessageID)
						   select Msg;

			foreach (Message Msg in Messages)
			{
				Msg.QueueStateID = (int)QueueStates.Sent;
				Msg.SendAttempts += 1;
			}

			Mqdb.SubmitChanges();
		}

		/// <summary>
		/// </summary>
		/// <returns></returns>
		public static Dictionary<int, MailMessage> GetEmailsToSend(int SendingWait, int FailedWait)
		{
			Dictionary<int, MailMessage> MessageList = new Dictionary<int, MailMessage>(0);
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();

			// Get Unsent (New) messages
			var Msgs = from Msg in Mqdb.Messages
					   where (Msg.QueueStateID == (int)QueueStates.Unsent)
					   select Msg;

			ChangeMessageQueueStateAndAddToList(Mqdb, Msgs, MessageList);

			// Get messages that got stuck in the sending state
			// Timestamp must be at least sendingWait min old and QueueStateID == (int)QueueState.Sending
			Msgs = from Msg in Mqdb.Messages
				   where (Msg.QueueStateID == (int)QueueStates.Sending)
						 && (Msg.LastProcessed <= DateTime.Now.AddMinutes(-SendingWait))
				   select Msg;

			ChangeMessageQueueStateAndAddToList(Mqdb, Msgs, MessageList);

			// Get failed messages to retry sending
			// Timestamp must be at least failedWait min old and QueueStateID == (int)QueueState.Failed
			Msgs = from Msg in Mqdb.Messages
				   where (Msg.QueueStateID == (int)QueueStates.Failed)
						 && (Msg.LastProcessed <= DateTime.Now.AddMinutes(-FailedWait))
				   select Msg;

			ChangeMessageQueueStateAndAddToList(Mqdb, Msgs, MessageList);

			return MessageList;
		}

		/// <summary>
		/// 	For Testing -- Get the IDs of all the messages in the database
		/// </summary>
		/// <returns>Integer array containing IDs of all messages in the database</returns>
		public static int[] GetAllMessageIDs()
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();

			var Msgs = from Msg in Mqdb.Messages
					   select Msg;

			int[] MessageIDs = new int[Msgs.Count()];
			int Counter = 0;

			foreach (Message Msg in Msgs)
			{
				MessageIDs[Counter++] = Msg.MessageID;
			}
			return MessageIDs;
		}

		/// <summary>
		/// </summary>
		/// <param name = "Msg"></param>
		/// <param name = "Ex"></param>
		/// <param name = "MsgKey"></param>
		/// <param name = "UserName"></param>
		/// <param name = "Password"></param>
		/// <param name = "SmtpServer"></param>
		/// <param name = "SmtpPort"></param>
		/// <param name = "UseDefaultCredentials"></param>
		/// <param name = "UseSsl"></param>
		/// <param name = "BccAddress"></param>
		/// <param name = "TrackMessage"></param>
		/// <returns></returns>
		public static bool Send(MailMessage Msg, ref MailQException Ex, int MsgKey, string UserName, string Password,
								string SmtpServer, int SmtpPort, bool UseDefaultCredentials, bool UseSsl, string BccAddress, string TrackMessage)
		{
			NetworkCredential Auth = new NetworkCredential(UserName, Password);
			SmtpClient Smtp = new SmtpClient(SmtpServer, SmtpPort);
			if (!UseDefaultCredentials)
			{
				Smtp.Credentials = Auth;
			}
			Smtp.EnableSsl = UseSsl;

			if (BccAddress.Length > 0)
			{
				Msg.Bcc.Add(new MailAddress(BccAddress, string.Empty));
			}

			if (TrackMessage.Length > 0)
			{
				string TrackingMessage = TrackMessage.Replace("[messageID]", MsgKey.ToString());
				if (Msg.IsBodyHtml)
				{
					Msg.Body += "<br /><br /><br /><br /><hr />";
					Msg.Body += TrackingMessage;
					Msg.Body += "<hr />";
					//msg.Body += "<br /><br />";
				}
				else
				{
					Msg.Body += "\r\n\r\n\r\n\r\n";
					Msg.Body += string.Format(".oO-  {0}  -Oo.", TrackingMessage);
					//msg.Body += "\r\n";
				}
			}

			try
			{
				Smtp.Send(Msg);
				return true;
			}
			catch (Exception Err)
			{
				Ex = (MailQException)Err;
				return false;
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "Msg"></param>
		/// <param name = "Ex"></param>
		/// <returns></returns>
		public static bool AddEmailToQueue(MailMessage Msg, ref MailQException Ex)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Message Message = new Message
							  {
								  BCCAddress = Msg.Bcc.ToString(),
								  Body = Msg.Body,
								  IsBodyHTML = Msg.IsBodyHtml,
								  CCAddress = Msg.CC.ToString(),
								  FromAddress = Msg.From.ToString(),
								  QueueStateID = (int)QueueStates.Unsent,
								  SendAttempts = 0,
								  Subject = Msg.Subject,
								  ToAddress = Msg.To.ToString(),
								  LastProcessed = DateTime.Now
							  };
			Mqdb.Messages.InsertOnSubmit(Message);
			try
			{
				Mqdb.SubmitChanges();
				return true;
			}
			catch (Exception Err)
			{
				Ex = (MailQException)Err;
				return false;
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "From"></param>
		/// <param name = "To"></param>
		/// <param name = "Cc"></param>
		/// <param name = "Bcc"></param>
		/// <param name = "Subject"></param>
		/// <param name = "Body"></param>
		/// <param name = "IsBodyHtml"></param>
		/// <param name = "Ex"></param>
		/// <returns></returns>
		public static bool AddEmailToQueue(string From, string To, string Cc, string Bcc, string Subject, string Body, bool IsBodyHtml, ref MailQException Ex)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Message Message = new Message
							  {
								  BCCAddress = Bcc,
								  Body = Body,
								  IsBodyHTML = IsBodyHtml,
								  CCAddress = Cc,
								  FromAddress = From,
								  LastProcessed = DateTime.Now,
								  QueueStateID = (int)QueueStates.Unsent,
								  SendAttempts = 0,
								  Subject = Subject,
								  ToAddress = To
							  };
			Mqdb.Messages.InsertOnSubmit(Message);
			try
			{
				Mqdb.SubmitChanges();
				return true;
			}
			catch (Exception Err)
			{
				Ex = (MailQException)Err;
				return false;
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "From"></param>
		/// <param name = "To"></param>
		/// <param name = "Cc"></param>
		/// <param name = "Bcc"></param>
		/// <param name = "Subject"></param>
		/// <param name = "Body"></param>
		/// <param name = "IsBodyHtml"></param>
		/// <param name = "Ex"></param>
		/// <returns></returns>
		public static bool AddEmailToQueue(string From, string[] To, string[] Cc, string[] Bcc, string Subject, string Body, bool IsBodyHtml, ref MailQException Ex)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Message Message = new Message
							  {
								  BCCAddress = String.Join(",", Bcc),
								  Body = Body,
								  IsBodyHTML = IsBodyHtml,
								  CCAddress = String.Join(",", Cc),
								  FromAddress = From,
								  LastProcessed = DateTime.Now,
								  QueueStateID = (int)QueueStates.Unsent,
								  SendAttempts = 0,
								  Subject = Subject,
								  ToAddress = String.Join(",", To)
							  };
			Mqdb.Messages.InsertOnSubmit(Message);
			try
			{
				Mqdb.SubmitChanges();
				return true;
			}
			catch (Exception Err)
			{
				Ex = (MailQException)Err;
				return false;
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "From"></param>
		/// <param name = "To"></param>
		/// <param name = "Cc"></param>
		/// <param name = "Bcc"></param>
		/// <param name = "Subject"></param>
		/// <param name = "Body"></param>
		/// <param name = "IsBodyHtml"></param>
		/// <param name = "Ex"></param>
		/// <returns></returns>
		public static bool AddEmailToQueue(MailAddress From, MailAddressCollection To, MailAddressCollection Cc, MailAddressCollection Bcc, string Subject, string Body, bool IsBodyHtml, ref MailQException Ex)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Message Message = new Message
							  {
								  BCCAddress = Bcc.ToString(),
								  Body = Body,
								  CCAddress = Cc.ToString(),
								  FromAddress = From.ToString(),
								  IsBodyHTML = IsBodyHtml,
								  LastProcessed = DateTime.Now,
								  ToAddress = To.ToString(),
								  Subject = Subject,
								  SendAttempts = 0,
								  QueueStateID = (int)QueueStates.Unsent
							  };

			Mqdb.Messages.InsertOnSubmit(Message);
			try
			{
				Mqdb.SubmitChanges();
				return true;
			}
			catch (Exception Err)
			{
				Ex = (MailQException)Err;
				return false;
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "EmailAddressesAsString"></param>
		/// <param name = "MailMessage"></param>
		/// <param name = "AddressType"></param>
		private static void ExtractEmailAddresses(string EmailAddressesAsString, MailMessage MailMessage,
												  AddressTypes AddressType)
		{
			foreach (string MailAddress in EmailAddressesAsString.Split(','))
			{
				switch (AddressType)
				{
					case AddressTypes.To:
						MailMessage.To.Add(new MailAddress(MailAddress));
						break;
					case AddressTypes.Bcc:
						MailMessage.Bcc.Add(new MailAddress(MailAddress));
						break;
					case AddressTypes.Cc:
						MailMessage.CC.Add(new MailAddress(MailAddress));
						break;
					default:
						throw new ArgumentOutOfRangeException("AddressType");
				}
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "Mqdb"></param>
		/// <param name = "Msgs"></param>
		/// <param name = "MessageList"></param>
		private static void ChangeMessageQueueStateAndAddToList(DataContext Mqdb, IEnumerable<Message> Msgs, IDictionary<int, MailMessage> MessageList)
		{
			foreach (Message Message in Msgs)
			{
				Message.QueueStateID = (int)QueueStates.Sending;
				Message.LastProcessed = DateTime.Now;
				MessageList.Add(Message.MessageID, new MailMessage
												   {
													   Body = Message.Body,
													   From = new MailAddress(Message.FromAddress),
													   IsBodyHtml = (Message.IsBodyHTML ?? true),
													   Subject = Message.Subject
												   });
				if (Message.BCCAddress.Length > 0)
				{
					ExtractEmailAddresses(Message.BCCAddress, MessageList[Message.MessageID], AddressTypes.Bcc);
				}
				if (Message.CCAddress.Length > 0)
				{
					ExtractEmailAddresses(Message.CCAddress, MessageList[Message.MessageID], AddressTypes.Cc);
				}
				if (Message.ToAddress.Length > 0)
				{
					ExtractEmailAddresses(Message.ToAddress, MessageList[Message.MessageID], AddressTypes.To);
				}
				MessageList[Message.MessageID].From = new MailAddress(Message.FromAddress);
			}

			Mqdb.SubmitChanges();
		}

		/// <summary>
		/// </summary>
		/// <param name = "WhatToLog"></param>
		/// <param name = "MessageId"></param>
		public static void AddToLog(string WhatToLog, int MessageId)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Log Log = new Log
					  {
						  Description = WhatToLog,
						  TimeStamp = DateTime.Now,
						  MessageID = MessageId
					  };
			Mqdb.Logs.InsertOnSubmit(Log);
			try
			{
				Mqdb.SubmitChanges();
			}
			catch (Exception Err)
			{
#pragma warning disable 168
				string TempStr = Err.Message;
#pragma warning restore 168
			}
		}

		/// <summary>
		/// </summary>
		/// <param name = "MessageId"></param>
		/// <returns></returns>
		public static EmailStatus GetEmailStatus(int MessageId)
		{
			MailQLiteDataContext Mqdb = new MailQLiteDataContext();
			Message Msg = (from X in Mqdb.Messages
						   where X.MessageID == MessageId
						   select X) as Message;
			if (Msg == null)
			{
				throw new Exception("Message does not exist");
			}
			EmailStatus Status = new EmailStatus
								 {
									 LastProcessed = Msg.LastProcessed ?? DateTime.MinValue,
									 Status = Msg.QueueState,
									 SendAttempts = Msg.SendAttempts ?? 0
								 };
			foreach (Log LogItem in Msg.Logs)
			{
				Status.LogEntries.Add(LogItem.Description);
			}
			return Status;
		}
	}
}